From 2849d8a6b3542795d230515a600670533adb2692 Mon Sep 17 00:00:00 2001 From: "C. Scott Ananian" Date: Fri, 12 Apr 2013 21:52:05 -0400 Subject: [PATCH] Create a Special:Redirect page. The primary purpose of this page is to redirect to a user page given a numeric id. The numeric User ID is stable across renames, and is therefore an appropriate primary key for identifying the user associated with a given revision. The Parsoid API would like to export semantic RDFa in its DOM identifying the author of a revision by their userid, but in order to do so requires a MW redirect from userid to the appropriate User page. (A "permalink" for the user.) This patch adds that redirect, as http://somewiki/Special:Redirect/user/1234 (https://bugzilla.wikimedia.org/show_bug.cgi?id=45206 is the related Parsoid feature.) Rather than adding a set of ad-hoc redirection pages, this patch sets up an infrastructure for redirections. Special:Redirect also subsumes the functions of: * Special:Filepath (Special:Redirect/file/xxxx) * Special:PermanentLink (Special:Redirect/revision/xxxxx) This structure is extensible for other redirect types. Change-Id: I8b0785f4fbdb3dd438a7a45263c5f375ff9d2208 --- includes/AutoLoader.php | 1 + includes/DefaultSettings.php | 2 +- includes/SpecialPageFactory.php | 1 + includes/specials/SpecialRedirect.php | 231 ++++++++++++++++++++++++++ languages/messages/MessagesEn.php | 14 ++ languages/messages/MessagesQqq.php | 14 ++ maintenance/language/messages.inc | 13 ++ 7 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 includes/specials/SpecialRedirect.php diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index 4813d45aca..07c9875b7a 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -968,6 +968,7 @@ $wgAutoloadLocalClasses = array( 'SpecialRandomredirect' => 'includes/specials/SpecialRandomredirect.php', 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php', 'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php', + 'SpecialRedirect' => 'includes/specials/SpecialRedirect.php', 'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php', 'SpecialSearch' => 'includes/specials/SpecialSearch.php', 'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php', diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index c19808118b..1f64b335e1 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -3305,7 +3305,7 @@ $wgMaxRedirects = 1; * As of now, this only checks special pages. Redirects to pages in * other namespaces cannot be invalidated by this variable. */ -$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' ); +$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk', 'Redirect' ); /** @} */ # End of title and interwiki settings } diff --git a/includes/SpecialPageFactory.php b/includes/SpecialPageFactory.php index 675fb833c4..6a73a1dfc7 100644 --- a/includes/SpecialPageFactory.php +++ b/includes/SpecialPageFactory.php @@ -161,6 +161,7 @@ class SpecialPageFactory { 'Mytalk' => 'SpecialMytalk', 'Myuploads' => 'SpecialMyuploads', 'PermanentLink' => 'SpecialPermanentLink', + 'Redirect' => 'SpecialRedirect', 'Revisiondelete' => 'SpecialRevisionDelete', 'Specialpages' => 'SpecialSpecialpages', 'Userlogout' => 'SpecialUserlogout', diff --git a/includes/specials/SpecialRedirect.php b/includes/specials/SpecialRedirect.php new file mode 100644 index 0000000000..5ea98d5774 --- /dev/null +++ b/includes/specials/SpecialRedirect.php @@ -0,0 +1,231 @@ +mType = null; + $this->mValue = null; + } + + /** + * Set $mType and $mValue based on parsed value of $subpage. + */ + function setParameter( $subpage ) { + // parse $subpage to pull out the parts + $parts = explode( '/', $subpage, 2 ); + $this->mType = count( $parts ) > 0 ? $parts[0] : null; + $this->mValue = count( $parts ) > 1 ? $parts[1] : null; + } + + /** + * Handle Special:Redirect/user/xxxx (by redirecting to User:YYYY) + * + * @return string|null url to redirect to, or null if $mValue is invalid. + */ + function dispatchUser() { + if ( !ctype_digit( $this->mValue ) ) { + return null; + } + $user = User::newFromId( (int)$this->mValue ); + $username = $user->getName(); // load User as side-effect + if ( $user->isAnon() ) { + return null; + } + $userpage = Title::makeTitle( NS_USER, $username ); + return $userpage->getFullURL( '', false, PROTO_CURRENT ); + } + + /** + * Handle Special:Redirect/file/xxxx + * + * @return string|null url to redirect to, or null if $mValue is not found. + */ + function dispatchFile() { + $title = Title::makeTitleSafe( NS_FILE, $this->mValue ); + + if ( ! $title instanceof Title ) { + return null; + } + $file = wfFindFile( $title ); + + if ( !$file || !$file->exists() ) { + return null; + } + // Default behavior: Use the direct link to the file. + $url = $file->getURL(); + $request = $this->getRequest(); + $width = $request->getInt( 'width', -1 ); + $height = $request->getInt( 'height', -1 ); + + // If a width is requested... + if ( $width != -1 ) { + $mto = $file->transform( array( 'width' => $width, 'height' => $height ) ); + // ... and we can + if ( $mto && !$mto->isError() ) { + // ... change the URL to point to a thumbnail. + $url = $mto->getURL(); + } + } + return $url; + } + + /** + * Handle Special:Redirect/revision/xxx + * (by redirecting to index.php?oldid=xxx) + * + * @return string|null url to redirect to, or null if $mValue is invalid. + */ + function dispatchRevision() { + $oldid = $this->mValue; + if ( !ctype_digit( $oldid ) ) { + return null; + } + $oldid = (int)$oldid; + if ( $oldid === 0 ) { + return null; + } + return wfAppendQuery( wfScript( 'index' ), array( + 'oldid' => $oldid + ) ); + } + + /** + * Use appropriate dispatch* method to obtain a redirection URL, + * and either: redirect, set a 404 error code and error message, + * or do nothing (if $mValue wasn't set) allowing the form to be + * displayed. + * + * @return bool true if a redirect was successfully handled. + */ + function dispatch() { + // the various namespaces supported by Special:Redirect + switch( $this->mType ) { + case 'user': + $url = $this->dispatchUser(); + break; + case 'file': + $url = $this->dispatchFile(); + break; + case 'revision': + $url = $this->dispatchRevision(); + break; + default: + $this->getOutput()->setStatusCode( 404 ); + $url = null; + break; + } + if ( $url ) { + $this->getOutput()->redirect( $url ); + return true; + } + if ( !is_null( $this->mValue ) ) { + $this->getOutput()->setStatusCode( 404 ); + $msg = $this->getMessagePrefix() . '-not-exists'; + return Status::newFatal( $msg ); + } + return false; + } + + protected function getFormFields() { + $mp = $this->getMessagePrefix(); + $ns = array( + // subpage => message + 'user' => $mp . '-user', + 'revision' => $mp . '-revision', + 'file' => $mp . '-file', + ); + $a = array(); + $a['type'] = array( + 'type' => 'select', + 'label-message' => $mp . '-lookup', + 'options' => array(), + 'default' => current( array_keys( $ns ) ), + ); + foreach( $ns as $n => $m ) { + $m = $this->msg( $m )->text(); + $a['type']['options'][$m] = $n; + } + $a['value'] = array( + 'type' => 'text', + 'label-message' => $mp . '-value' + ); + // set the defaults according to the parsed subpage path + if ( !empty( $this->mType ) ) { + $a['type']['default'] = $this->mType; + } + if ( !empty( $this->mValue ) ) { + $a['value']['default'] = $this->mValue; + } + return $a; + } + + public function onSubmit( array $data ) { + if ( !empty( $data['type'] ) && !empty( $data['value'] ) ) { + $this->setParameter( $data['type'] . '/' . $data['value'] ); + } + /* if this returns false, will show the form */ + return $this->dispatch(); + } + + public function onSuccess() { + /* do nothing, we redirect in $this->dispatch if successful. */ + } + + protected function alterForm( HTMLForm $form ) { + /* display summary at top of page */ + $this->outputHeader(); + /* tweak label on submit button */ + $form->setSubmitTextMsg( $this->getMessagePrefix() . '-submit' ); + /* submit form every time */ + $form->setMethod( 'get' ); + } + + protected function getGroupName() { + return 'redirects'; + } +} diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 8191f590a9..c41d935e1a 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -447,6 +447,7 @@ $specialPageAliases = array( 'Randomredirect' => array( 'RandomRedirect' ), 'Recentchanges' => array( 'RecentChanges' ), 'Recentchangeslinked' => array( 'RecentChangesLinked', 'RelatedChanges' ), + 'Redirect' => array( 'Redirect' ), 'Revisiondelete' => array( 'RevisionDelete' ), 'Search' => array( 'Search' ), 'Shortpages' => array( 'ShortPages' ), @@ -4794,6 +4795,19 @@ You should have received [{{SERVER}}{{SCRIPTPATH}}/COPYING a copy of the GNU Gen 'filepath-summary' => 'This special page returns the complete path for a file. Images are shown in full resolution, other file types are started with their associated program directly.', +# Special:Redirect +'redirect' => 'Redirect by file, user, or revision ID', +'redirect-legend' => 'Redirect to a file or page', +'redirect-text' => '', +'redirect-summary' => 'This special page redirects to a file (given the file name), a page (given a revision ID), or a user page (given a numeric user ID).', +'redirect-submit' => 'Go', +'redirect-lookup' => 'Lookup:', +'redirect-value' => 'Value:', +'redirect-user' => 'User ID', +'redirect-revision' => 'Page revision', +'redirect-file' => 'File name', +'redirect-not-exists' => 'Value not found', + # Special:FileDuplicateSearch 'fileduplicatesearch' => 'Search for duplicate files', 'fileduplicatesearch-summary' => 'Search for duplicate files based on hash values.', diff --git a/languages/messages/MessagesQqq.php b/languages/messages/MessagesQqq.php index 4ef67e50b9..1ebf777a1d 100644 --- a/languages/messages/MessagesQqq.php +++ b/languages/messages/MessagesQqq.php @@ -8514,6 +8514,20 @@ A short description of the script path entry point. Links to the mediawiki.org d {{Identical|Go}}', 'filepath-summary' => 'Shown in [[Special:FilePath]]', +# Special:Redirect +'redirect' => 'Main heading of [[Special:Redirect]] page', +'redirect-legend' => 'Legend of fieldset around input box in [[Special:Redirect]]', +'redirect-text' => 'Inside fieldset for [[Special:Redirect]]', +'redirect-summary' => 'Shown at top of [[Special:Redirect]]', +'redirect-submit' => 'Button label in [[Special:Redirect]]. +{{Identical|Go}}', +'redirect-lookup' => 'First field label in [[Special:Redirect]]', +'redirect-value' => 'Second field label in [[Special:Redirect]]', +'redirect-user' => 'Description of lookup type for [[Special:Redirect]]', +'redirect-revision' => 'Description of lookup type for [[Special:Redirect]]', +'redirect-file' => 'Description of lookup type for [[Special:Redirect]]', +'redirect-not-exists' => 'Used as error message in [[Special:Redirect]]', + # Special:FileDuplicateSearch 'fileduplicatesearch' => 'Name of special page [[Special:FileDuplicateSearch]].', 'fileduplicatesearch-summary' => 'Summary of [[Special:FileDuplicateSearch]]', diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index 96a6c13e4f..deb01d5d99 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -3643,6 +3643,19 @@ $wgMessageStructure = array( 'filepath-submit', 'filepath-summary', ), + 'redirect' => array( + 'redirect', + 'redirect-legend', + 'redirect-text', + 'redirect-summary', + 'redirect-submit', + 'redirect-lookup', + 'redirect-value', + 'redirect-user', + 'redirect-revision', + 'redirect-file', + 'redirect-not-exists', + ), 'fileduplicatesearch' => array( 'fileduplicatesearch', 'fileduplicatesearch-summary', -- 2.20.1